package zilvic.game.room;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import org.duncan.Math2D.Point2D;
import org.duncan.Math2D.Rect2D;
import zilvic.game.Game;
import zilvic.game.KeyInputHandler;
import zilvic.game.room.entity.actor.Actor;
import zilvic.game.room.entity.effect.Effect;
import zilvic.game.room.entity.trigger.Timer;
import zilvic.game.room.entity.trigger.Trigger;
import zilvic.game.room.tile.Tile;
import zilvic.resources.ByteMagic;
import zilvic.resources.Parsable;

/**
 *
 * @author Duncan
 */
public class Room implements Parsable {
    private String title    = "";
    private String subtitle = "";

    private Point2D size = new Point2D(0, 0);

    public final static byte LAYER_FLOOR  = 0;
    public final static byte LAYER_WALLS  = 1;
    public final static byte LAYER_ROOF   = 2;
    public final static byte LAYER_COUNT  = 3;

    private Tile[] tiles;
    private ArrayList<Actor> actors     = new ArrayList<Actor>();
    private ArrayList<Trigger> triggers = new ArrayList<Trigger>();
    private ArrayList<Effect> effects   = new ArrayList<Effect>();
    private ArrayList<Timer> timers     = new ArrayList<Timer>();

    public Room(int width, int height) {
        tiles = new Tile[width * height];
        size.setX(width);
        size.setY(height);
    }

    public Room setTile(Tile tile, int index) {
        tiles[index] = tile;
        return this;
    }

    public Tile getTile(int index) {
        return tiles[index];
    }

    public int indexOf(Point2D pos) {
        return (int) ((int) (pos.getY() / getHeight())) + ((int) pos.getX());
    }

    public boolean contains(Point2D pos) {
        return (pos.getX() >= 0 && pos.getX() < getWidth()
            &&  pos.getY() >= 0 && pos.getY() < getHeight());
    }

    public int getWidth() {
        return (int) size.getX();
    }

    public int getHeight() {
        return (int) size.getY();
    }

    public void init(int status) {

    }

    public void step(long delta) {
        for (Actor a : actors) {
            a.step(delta);
        }

        for (int i = 0; i < effects.size(); i++) {
            if (effects.get(i).tick(delta)) {
                effects.remove(i);
                i--;
            }
        }

        for (int i = 0; i < timers.size(); i++) {
            if (timers.get(i).tick(delta)) {
                timers.remove(i);
                i--;
            }
        }
    }

    public void close() {

    }

    public Room addTrigger(Trigger trigger) {
        triggers.add(trigger);
        return this;
    }

    public Room addActor(Actor actor) {
        actors.add(actor);
        return this;
    }

    public Room addEffect(Effect effect) {
        effects.add(effect);
        return this;
    }

    public Room addTimer(Timer timer) {
        timers.add(timer);
        return this;
    }

    public Room removeTrigger(Trigger trigger) {
        triggers.remove(trigger);
        return this;
    }

    public Room removeActor(Actor actor) {
        actors.remove(actor);
        return this;
    }

    public Room removeEffect(Effect effect) {
        effects.remove(effect);
        return this;
    }

    public Room removeTimer(Timer timer) {
        timers.remove(timer);
        return this;
    }

    public Room setTitle(String title) {
        this.title = title;
        return this;
    }

    public Room setSubtitle(String subtitle) {
        this.subtitle = subtitle;
        return this;
    }

    public String getTitle() {
        return this.title;
    }

    public String getSubtitle() {
        return this.subtitle;
    }

    public boolean collideWith(Point2D position) {
        if (!contains(position)) return false;
        if (getTile(indexOf(position)).isSolid())
            return false;
        for (Actor a : actors) {
            if (getTile(indexOf(position)).contains(a.getPosition())) {
                if (a.collideWith()) {
                    return true;
                }
                if (Game.getInputHandler().isKeyDown(KeyInputHandler.KEY_FIRST_HAND))
                    a.firstHandInteraction();
                else if(Game.getInputHandler().isKeyDown(KeyInputHandler.KEY_SECOND_HAND))
                    a.secondHandInteraction();
            }
        }

        for (Trigger t : triggers) {
            if (getTile(indexOf(position)).contains(t.getPosition())) {
                if (t.collideWith()) {
                    return true;
                }
                if (Game.getInputHandler().isKeyDown(KeyInputHandler.KEY_FIRST_HAND))
                    t.firstHandInteraction();
                else if(Game.getInputHandler().isKeyDown(KeyInputHandler.KEY_SECOND_HAND))
                    t.secondHandInteraction();
            }
        }
        return true;
    }

    /**
     * Draws the given region of this room to the given surface.
     * The room will be rendered in the following order:
     *      The floor-tiles.
     *      The wall-tiles.
     *      Triggers.
     *      Actors.
     *      The character.
     *      The roof-tiles.
     *      Effects.
     * @param g The graphics-surface to draw to.
     * @param step The step to use for animated sprites.
     * @param region The render-region.
     * @return A reference to this.
     */
    public Room draw(Graphics g, byte step, Rect2D region) {
        BufferedImage[] buf = new BufferedImage[LAYER_COUNT];
        for (int layer = 0; layer < LAYER_COUNT; layer++) {
            buf[layer] = new BufferedImage(
                    (int) region.getWidth(),
                    (int) region.getHeight(),
                    BufferedImage.TYPE_INT_ARGB);
        }

        for (int x = (int) region.getStart().getX(); x < (int) region.getEnd().getX(); x++) {
            for (int y = (int) region.getStart().getY(); y < (int) region.getEnd().getY(); y++) {
                getTile(indexOf(new Point2D(x, y).add(region.getStart().clone().reverse()))).render(
                        buf[LAYER_FLOOR].getGraphics(),
                        buf[LAYER_WALLS].getGraphics(),
                        buf[LAYER_ROOF].getGraphics());
            }
        }

        g.drawImage(buf[LAYER_FLOOR], 0, 0, null);
        g.drawImage(buf[LAYER_WALLS], 0, 0, null);

        for (Trigger t : triggers) {
            if (region.contains(t.getPosition()))
                t.paint(g, step);
        }

        for (Actor a : actors) {
            if (region.contains(a.getPosition()))
                a.paint(g, step);
        }

        Game.getInstance().getPlayer().paint(g, step);

        g.drawImage(buf[LAYER_ROOF], 0, 0, null);

        for (Effect e : effects) {
            if (region.contains(e.getPosition()))
                e.paint(g, step);
        }

        return this;
    }

    /**
     * The format is as follows.
     * 0:  The title.
     * 1:  The subtitle.
     * 2:  The size as a Point2D.
     * 3:  The tiles. (Tile.toBytes()).
     * 4:  The actors.
     * 5:  The triggers.
     * 6:  The effects.
     * 7:  The timers.
     * @return This room in bytes.
     */
    public byte[] toBytes() {
        return ByteMagic.compressBytes(new byte[][]{
            ByteMagic.stringToBytes(title),
            ByteMagic.stringToBytes(subtitle),
            size.toBytes(),
            ByteMagic.parsablesToBytes(tiles),
            ByteMagic.parsablesToBytes((Actor[]) actors.toArray()),
            ByteMagic.parsablesToBytes((Trigger[]) triggers.toArray()),
            ByteMagic.parsablesToBytes((Effect[]) effects.toArray()),
            ByteMagic.parsablesToBytes((Timer[]) timers.toArray())});
    }

    public void fromBytes(byte[] bytes) {
        byte[][] u = ByteMagic.uncompressBytes(bytes);
        title    = ByteMagic.bytesToString(u[0]);
        subtitle = ByteMagic.bytesToString(u[1]);
        size.fromBytes(u[2]);
        tiles    = (Tile[]) ByteMagic.bytesToParsables(u[3]);
        actors   = new ArrayList<Actor>(Arrays.asList((Actor[]) ByteMagic.bytesToParsables(u[4])));
        triggers = new ArrayList<Trigger>(Arrays.asList((Trigger[]) ByteMagic.bytesToParsables(u[5])));
        effects  = new ArrayList<Effect>(Arrays.asList((Effect[]) ByteMagic.bytesToParsables(u[6])));
        timers   = new ArrayList<Timer>(Arrays.asList((Timer[]) ByteMagic.bytesToParsables(u[7])));
    }
}